-- AnimationCacheM
-- create by zouyb
-- csb动画缓存模块， 主要用户特效和怪物动画的缓存
-- 缓存常用的特效，防止创建节点导致性能下降

module("AnimationCacheM", package.seeall);

MAX_CACHE_NUM = 2;

local freeCachePool = {};
local playingCachePool = {};
local createMap = {};
local animCount = {};
local cacheRequestCount = 0;
local totalRequestCount = 0;

-- 由于动画在移除时，可能出现没调用 freeAnimationNodeAndAction 的情况，所以需要定时检查以便回收
local function timer_handler()
    -- 遍历 playingCachePool
    for name, info in pairs(playingCachePool) do
        local flag = false;
        for i, node in ipairs(info.node) do
            if not node:getParent() then
                -- 该节点被移除，需要移到 freeCachePool 中
                info.node[i] = nil;
                local action = info.action[i];
                info.action[i] = nil;

                freeCachePool[name] = freeCachePool[name] or {};
                freeCachePool[name]["node"] = freeCachePool[name]["node"] or {};
                freeCachePool[name]["action"] = freeCachePool[name]["action"] or {};
                table.insert(freeCachePool[name]["node"], node);
                table.insert(freeCachePool[name]["action"], action);

                cclog("AnimationCacheM timer_handler recache animation : %o\n", name);
                flag = true;
            end
        end

        if flag then
            clean_array(info.node);
            clean_array(info.action);
        end
    end
end

function create()

    -- 设置定时器，定时检查未回收的动画资源
    --ScheduleM.createScheme("AnimationCacheM", timer_handler, 5, true);

    freeCachePool = get_daemon_data("AnimationCacheM", "freeCachePool") or {};
    playingCachePool = get_daemon_data("AnimationCacheM", "playingCachePool") or {};
    createMap = get_daemon_data("AnimationCacheM", "createMap") or {};
    animCount = get_daemon_data("AnimationCacheM", "animCount") or {};
end

function destruct()
    --ScheduleM.deleteScheme("AnimationCacheM");

    register_daemon_data("AnimationCacheM", "freeCachePool", freeCachePool);
    register_daemon_data("AnimationCacheM", "playingCachePool", playingCachePool);
    register_daemon_data("AnimationCacheM", "createMap", createMap);
    register_daemon_data("AnimationCacheM", "animCount", animCount);
end

-- 获取动画
function createAnimationNodeAndAction(fileName)
    -- 记录指定动画 create 次数
    createMap[fileName] = createMap[fileName] or 0;
    createMap[fileName] = createMap[fileName] + 1;

    -- 记录总的请求数
    totalRequestCount = totalRequestCount + 1;

    freeCachePool[fileName] = freeCachePool[fileName] or {};
    playingCachePool[fileName] = playingCachePool[fileName] or {};
    playingCachePool[fileName]["node"] = playingCachePool[fileName]["node"] or {};

    if freeCachePool[fileName]["node"] and #freeCachePool[fileName]["node"] > 0 then
        -- 存在可用的动画缓存
        local node = freeCachePool[fileName]["node"][1];
        local action = freeCachePool[fileName]["action"][1];

        if node:isExisted() and action:isExisted() then
            -- 将动画从 free 缓存池中移到 playing 缓存池中
            table.removeItem(freeCachePool[fileName]["node"], node);
            table.removeItem(freeCachePool[fileName]["action"], action);

            playingCachePool[fileName]["action"] = playingCachePool[fileName]["action"] or {};
            table.insert(playingCachePool[fileName]["node"], node);
            table.insert(playingCachePool[fileName]["action"], action);

            -- 命中缓存
            cacheRequestCount = cacheRequestCount + 1;

            --cclog("AnimationCacheM reuse animation : %s\n", fileName);

            -- 尝试还原着色
            resetNormalMode(node);

            node:stopAllActions();

            return node, action;
        end

        -- 异常分支
        if isWindowsPlatform() then
            error("createAnimationNodeAndAction node isnot existed!");
        end

        -- 将该无效的节点从缓存中移除
        if node:isExisted() then
            node:release();
        end
        freeCachePool[fileName]["node"][1] = nil;
        if action:isExisted() then
            action:release();
        end
        freeCachePool[fileName]["action"][1] = nil;

        clean_array(freeCachePool[fileName]["node"]);
        clean_array(freeCachePool[fileName]["action"]);
        animCount[fileName] = animCount[fileName] - 1;
    else
        -- 检查 playing 缓存池中是否有可用的动画（动画已被移除，但没有回收回 free 缓存池中）
        for i, node in ipairs(playingCachePool[fileName]["node"]) do
            if not node:isExisted() then
                if isWindowsPlatform() then
                    error("createAnimationNodeAndAction node isnot existed!");
                end

                -- 将该无效的节点从缓存中移除
                playingCachePool[fileName]["node"][i] = nil;
                local action = playingCachePool[fileName]["action"][i];
                if action:isExisted() then
                    action:release();
                end
                playingCachePool[fileName]["action"][i] = nil;

                clean_array(playingCachePool[fileName]["node"]);
                clean_array(playingCachePool[fileName]["action"]);
                animCount[fileName] = animCount[fileName] - 1;
                break;
            end

            if not node:getParent() then
                -- 该动画节点没有父节点，表示已被移除，可直接被再次使用
                --cclog("AnimationCacheM reuse playing animation : %s\n", fileName);

                -- 命中缓存
                cacheRequestCount = cacheRequestCount + 1;

                -- 尝试还原着色
                resetNormalMode(node);

                node:stopAllActions();

                return node, playingCachePool[fileName]["action"][i];
            end
        end
    end

    -- 没有可用缓存的动画，则要创建新的动画

    local node = cc.CSLoader:createNode(fileName);
    local action = cc.CSLoader:createTimeline(fileName);

    animCount[fileName] = animCount[fileName] or 0;
    if animCount[fileName] >= MAX_CACHE_NUM then
        -- 不缓存
        -- cclog("AnimationCacheM cache upto limit : %s\n", fileName);
        return node, action;
    end

    -- 将动画放入 playing 缓存池中
    node.createByCacheM = true;
    node:retain();
    action.createByCacheM = true;
    action:retain();

    playingCachePool[fileName]["action"] = playingCachePool[fileName]["action"] or {};
    table.insert(playingCachePool[fileName]["node"], node);
    table.insert(playingCachePool[fileName]["action"], action);

    animCount[fileName] = animCount[fileName] + 1;

    return node, action;
end

-- 释放动画
function freeAnimationNodeAndAction(fileName, node, action)
    createMap[fileName] = createMap[fileName] and createMap[fileName] - 1;

    if not playingCachePool[fileName] or not node.createByCacheM then
        return;
    end

    if node:isExisted() then
        node:removeFromParent();
    end

    -- 将动画从 playing 缓存池中移到 free 缓存池中
    local ret = table.removeItem(playingCachePool[fileName]["node"], node);
    if ret < 0 then
        --assert(false, string.format("freeAnimationNodeAndAction node error : %s", fileName));
        return;
    end
    ret = table.removeItem(playingCachePool[fileName]["action"], action);
    if ret < 0 then
        --assert(false, string.format("freeAnimationNodeAndAction action error : %s", fileName));
        return;
    end

    freeCachePool[fileName]["node"] = freeCachePool[fileName]["node"] or {};
    freeCachePool[fileName]["action"] = freeCachePool[fileName]["action"] or {};
    table.insert(freeCachePool[fileName]["node"], node);
    table.insert(freeCachePool[fileName]["action"], action);
end

function getPlayingAnimations()
    return createMap;
end

function getAnimCount()
    local totalCount = 0;
    for _, count in pairs(animCount) do
        totalCount = totalCount + count;
    end
    return animCount, totalCount;
end

function getPlayingCache()
    return playingCachePool;
end

function getFreeCache()
    return freeCachePool;
end

function getCacheRate()
    return cacheRequestCount / totalRequestCount, cacheRequestCount, totalRequestCount;
end

-- 清空缓存
function purgeCache()
    Profiler.funcBegin("ActionTimelineCache:purge");
    ccs.ActionTimelineCache:getInstance():purge();
    Profiler.funcEnd("ActionTimelineCache:purge");

    for name, info in pairs(playingCachePool) do
        if info.node then
            for _, node in ipairs(info.node) do
                if node:isExisted() then
                    Profiler.funcBegin("ActionTimelineCache:releaseNode");
                    node:release();
                    Profiler.funcEnd("ActionTimelineCache:releaseNode");
                end
            end
        end

        if info.action then
            for _, action in ipairs(info.action) do
                if action:isExisted() then
                    Profiler.funcBegin("ActionTimelineCache:releaseAction");
                    action:release();
                    Profiler.funcEnd("ActionTimelineCache:releaseAction");
                end
            end
        end
    end
    playingCachePool = {};

    for name, info in pairs(freeCachePool) do
        if info.node then
            for _, node in ipairs(info.node) do
                if node:isExisted() then
                    Profiler.funcBegin("ActionTimelineCache:releaseNode");
                    node:release();
                    Profiler.funcEnd("ActionTimelineCache:releaseNode");
                end
            end
        end

        if info.action then
            for _, action in ipairs(info.action) do
                if action:isExisted() then
                    Profiler.funcBegin("ActionTimelineCache:releaseAction");
                    action:release();
                    Profiler.funcEnd("ActionTimelineCache:releaseAction");
                end
            end
        end
    end
    freeCachePool = {};

    animCount = {};
    createMap = {};
    cacheRequestCount = 0;
    totalRequestCount = 0;
end

create();
